package com.wcy.app.lib.dex

import android.content.Context
import android.content.pm.PackageManager
import dalvik.system.DexClassLoader
import dalvik.system.PathClassLoader
import java.io.File
import java.lang.reflect.Array

class FixDexUtil {
    private val DEX_SUFFIX = ".dex"
    private val APK_SUFFIX = ".apk"
    private val JAR_SUFFIX = ".jar"
    private val ZIP_SUFFIX = ".zip"
    val DEX_DIR = "odex"
    private val OPTIMIZE_DEX_DIR = "optimize_dex"
    private var mContext: Context? = null

    companion object {
        private var fixDexUtil: FixDexUtil? = null
        fun getInstance(): FixDexUtil {
            if (fixDexUtil == null) {
                synchronized(FixDexUtil::class.java) {
                    if (fixDexUtil == null) {
                        fixDexUtil = FixDexUtil()
                    }
                }
            }
            return fixDexUtil!!
        }

    }

    fun init(context: Context, key: String?, isDebug: Boolean) {
        mContext = context
        LogUtil.isDebug = isDebug
        val runnable = Runnable {
            deleFile(getDexFile())
            DownDex.post(key!!, DeviceIdUtil.getMac(context))
        }
        val mythread = Thread(runnable)
        mythread.start()
    }

    fun init(context: Context, key: String?) {
        init(context, key, false)
    }

    /**
     * 加载补丁，使用默认目录：data/data/包名/files/odex
     */
    fun loadFixedDex(fileName: String) {
        loadFixedDex(File(getJarPaht(), fileName))
    }

    fun loadFixedDex() {
        val file = getJarFile()
        if (file != null) {
            val files = file.listFiles()
            if (files != null && files.isNotEmpty()) {
                LogUtil.log("开始更新dex文件--(" + files.size + ")----")
                for (file1 in files) {
                    if (file1.name.endsWith(DEX_SUFFIX) || file1.name.endsWith(JAR_SUFFIX) || file1.name.endsWith(APK_SUFFIX) || file1.name.endsWith(ZIP_SUFFIX)) {
                        loadFixedDex(file1)
                        break
                    }
                }
            }
        }
    }

    /**
     * 加载补丁
     */
    private fun loadFixedDex(loadedDex: File?) {
        try {
            deleFile(getDexFile())
            if (loadedDex != null) {
                if (loadedDex != null) {
                    if (!loadedDex.name.startsWith(getAppVersionCode().toString() + "")) {
                        return
                    }
                    LogUtil.log("loadedDex路径：" + loadedDex.path + ":名称:" + loadedDex.name)
                    val fopt = getDexFile()
                    // 1.加载应用程序dex的Loader
                    val pathLoader = mContext!!.classLoader as PathClassLoader
                    // 2.加载指定的修复的dex文件的Loader
                    val dexLoader = DexClassLoader(loadedDex.absolutePath,  // 修复好的dex（补丁）所在目录
                            fopt.absolutePath,  // 存放dex的解压目录（用于jar、zip、apk格式的补丁）
                            null,  // 加载dex时需要的库
                            pathLoader // 父类加载器
                    )
                    // 3.开始合并
                    // 合并的目标是Element[],重新赋值它的值即可
                    /**
                     * BaseDexClassLoader中有 变量: DexPathList pathList
                     * DexPathList中有 变量 Element[] dexElements
                     * 依次反射即可
                     */

                    //3.1 准备好pathList的引用
                    val dexPathList = getPathList(dexLoader)
                    val pathPathList = getPathList(pathLoader)
                    //3.2 从pathList中反射出element集合
                    val leftDexElements = getDexElements(dexPathList)
                    val rightDexElements = getDexElements(pathPathList)
                    //3.3 合并两个dex数组
                    val dexElements = combineArray(leftDexElements, rightDexElements)
                    // 重写给PathList里面的Element[] dexElements;赋值
                    val pathList = getPathList(pathLoader) // 一定要重新获取，不要用pathPathList，会报错
                    setField(pathList, pathList.javaClass, Packet.dexElements, dexElements)
                    LogUtil.log("修复完成")
                }
            }
        } catch (e: Exception) {
            LogUtil.log("修复失败")
        }
    }

    fun detaleJar() {
        deleFile(getJarFile())
    }

    /**
     * 是否有新版本
     *
     * @param ver
     * @return
     */
    fun isLatestVersion(ver: Int): Boolean {
        var falg = true
        try {
            val file = getJarFile()
            val files = file.listFiles()
            if (files != null && files.isNotEmpty()) {
                for (f in files) {
                    var fName = f.name
                    if (fName.endsWith(APK_SUFFIX) || fName.endsWith(DEX_SUFFIX) || fName.endsWith(JAR_SUFFIX) || fName.endsWith(ZIP_SUFFIX)) {
                        fName = fName.substring(0, fName.indexOf("."))
                        val versions = fName.split("_".toRegex()).toTypedArray()
                        if (versions.size == 2) {
                            val v = versions[1].toInt()
                            if (v >= ver) {
                                falg = false
                            }
                        }
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return falg
    }

    private fun deleFile(files: File?) {
        if (files != null) {
            val odexs = files.listFiles()
            if (odexs != null && odexs.isNotEmpty()) {
                for (file in odexs) {
                    LogUtil.log("删除：" + file.path)
                    file.delete()
                }
            }
        }
    }


    fun getJarPaht(): String? {
        return getJarFile().path
    }

    private fun getDexFile(): File {
        val optimizeDir = mContext!!.filesDir.absolutePath + File.separator + OPTIMIZE_DEX_DIR
        // data/da ta/包名/files/optimize_dex（这个必须是自己程序下的目录）
        val fopt = File(optimizeDir)
        if (!fopt.exists()) {
            fopt.mkdirs()
        }
        LogUtil.log("创建dex：" + fopt.path)
        return fopt
    }

    private fun getJarFile(): File {
        //        File externalStorageDirectory = Environment.getExternalStorageDirectory();
//        // 遍历所有的修复dex , 因为可能是多个dex修复包
//        File fileDir = externalStorageDirectory != null ? externalStorageDirectory : context.getFilesDir();// data/data/包名/files/odex（这个可以任意位置）
        //   File externalStorageDirectory = Environment.getExternalStorageDirectory();
        //   File path = new File(externalStorageDirectory, DEX_DIR + "/" + getPackageName());
        val externalStorageDirectory = mContext!!.filesDir
        val path = File(externalStorageDirectory, DEX_DIR)
        if (!path.exists()) {
            path.mkdirs()
        }
        return path
    }

    /**
     * 获取App版本码
     *
     * @return App版本码
     */
    fun getAppVersionCode(): Int {
        return try {
            val pm = mContext!!.packageManager
            val pi = pm.getPackageInfo(mContext!!.packageName, 0)
            return pi?.versionCode ?: -1
        } catch (e: PackageManager.NameNotFoundException) {
            if (LogUtil.isDebug) e.printStackTrace()
            -1
        }
    }

    fun getPackageName(): String? {
        return mContext!!.packageName
    }

    /**
     * 反射给对象中的属性重新赋值
     */
    @Throws(NoSuchFieldException::class, IllegalAccessException::class)
    private fun setField(obj: Any, cl: Class<*>, field: String, value: Any) {
        val declaredField = cl.getDeclaredField(field)
        declaredField.isAccessible = true
        declaredField[obj] = value
    }

    /**
     * 反射得到对象中的属性值
     */
    @Throws(NoSuchFieldException::class, IllegalAccessException::class)
    private fun getField(obj: Any, cl: Class<*>, field: String): Any {
        val localField = cl.getDeclaredField(field)
        localField.isAccessible = true
        return localField[obj]
    }


    /**
     * 反射得到类加载器中的pathList对象
     */
    @Throws(ClassNotFoundException::class, NoSuchFieldException::class, IllegalAccessException::class)
    private fun getPathList(baseDexClassLoader: Any): Any {
        return getField(baseDexClassLoader, Class.forName(Packet.baseDexClassLoader), Packet.pathList)
    }

    /**
     * 反射得到pathList中的dexElements
     */
    @Throws(NoSuchFieldException::class, IllegalAccessException::class)
    private fun getDexElements(pathList: Any): Any {
        return getField(pathList, pathList.javaClass, Packet.dexElements)
    }

    /**
     * 数组合并
     */
    private fun combineArray(arrayLhs: Any, arrayRhs: Any): Any {
        val clazz = arrayLhs.javaClass.componentType
        val i = Array.getLength(arrayLhs) // 得到左数组长度（补丁数组）
        val j = Array.getLength(arrayRhs) // 得到原dex数组长度
        val k = i + j // 得到总数组长度（补丁数组+原dex数组）
        val result = Array.newInstance(clazz, k) // 创建一个类型为clazz，长度为k的新数组
        System.arraycopy(arrayLhs, 0, result, 0, i)
        System.arraycopy(arrayRhs, 0, result, i, j)
        return result
    }
}